#include "ohos_video_render_base.h"
#include "rtc_base/logging.h"
#include "common_video/util/frame_util.h"

namespace webrtc {
namespace {
constexpr const char *TAG = "BaseVideoRender";
}

BaseVideoRender::BaseVideoRender() : tid_("[" + std::string(TAG) + "@" + std::to_string(reinterpret_cast<uintptr_t>(this)) +"]"),
                                     egl_render_(nullptr),
                                     mirror_(false),
                                     frame_drawer_(nullptr),
                                     video_size_measure_(std::make_shared<VideoSizeMeasure>()),
                                     draw_matrix_(std::make_shared<Matrix>()) {}

bool BaseVideoRender::Init(void* window, void* share_context, int width, int height) {
  if (inited_) {
    return true;
  }
  RTC_LOG(LS_INFO) << TAG << " Init. window: " << window << ", context: "<< share_context << ", width: " << width << ", height: " << height << GetTid();

  egl_render_ = std::make_shared<EGLRender>();
  frame_drawer_ = std::make_shared<VideoFrameDrawer>();

  auto result = egl_render_->InitContext(window, share_context, width, height);
  RTC_CHECK(result) << TAG << " Init, failed. this: " << GetTid();
  UpdateSurfaceSize(width, height);

  inited_  = true;
  return result;
}

void BaseVideoRender::SizeChange(int width, int height) {
  if (surface_width_ != width || surface_height_ != height) {
    UpdateSurfaceSize(width, height);    
  }
}

void BaseVideoRender::UpdateSurfaceSize(int width, int height) {
  if (egl_render_) {
    egl_render_->UpdateSize(width, height);
  }
  
  surface_width_ = width;
  surface_height_ = height;
  RTC_LOG(LS_INFO) << TAG << " UpdateSurfaceSize. " << surface_width_ << "x" << surface_height_ << GetTid();
  UpdateLayoutAspectRatio();
}

void BaseVideoRender::ClearColor(const std::vector<float>& rgba_color) {
  RTC_LOG(LS_INFO) << TAG << " ClearColor." << GetTid();

  if (egl_render_) {
    egl_render_->ClearColor(rgba_color.data());
  }
}

void BaseVideoRender::OnFrame(const webrtc::VideoFrame& frame) {
  if (!egl_render_ || !egl_render_->HasSurface()) {
    RTC_LOG(LS_INFO) << TAG << " Dropping frame - No surface." << GetTid();
    return;
  }
  if (!frame_drawer_) {
    RTC_LOG(LS_INFO) << TAG << " Dropping frame - No drawer" << GetTid();
    return;
  }

  if (!is_first_frame_rendered_) {
    is_first_frame_rendered_ = true;
    for (auto &listener : render_listeners_) {
      listener->OnFirstFrameRendered();
    }
    RTC_LOG(LS_INFO) << TAG << " Reporting first rendered frame." << ", tid: " << GetTid();
  }

  int frame_rotation = frame.rotation();
  int frame_w = FrameUtil::GetFrameRotatedWidth(frame);
  int frame_h = FrameUtil::GetFrameRotatedHeight(frame);

  int viewport_width, viewport_height, viewport_x, viewport_y;
  CalculateDrawProperties(frame_w, frame_h, frame_rotation, viewport_x, viewport_y, viewport_width, viewport_height);
  frame_drawer_->DrawFrame(egl_render_, frame, draw_matrix_, viewport_x, viewport_y, viewport_width, viewport_height);
}


void BaseVideoRender::CalculateDrawProperties(int frame_width,
                                              int frame_height,
                                              int frame_rotation,
                                              int &viewport_x,
                                              int &viewport_y,
                                              int &viewport_width,
                                              int &viewport_height) {
  std::unique_lock<std::mutex> lock(layout_lock_);
  UpdateFrameDimensions(frame_width, frame_height, frame_rotation);
  float frame_ratio = static_cast<float>(frame_width_) / static_cast<float>(frame_height_);
  float drawn_ratio = layout_aspect_ratio_ > 0 ? layout_aspect_ratio_ : frame_ratio;

  float scale_x;
  float scale_y;

  if (frame_ratio > drawn_ratio) {
    scale_x = drawn_ratio / frame_ratio;
    scale_y = 1.f;
  } else {
    scale_x = 1.f;
    scale_y = frame_ratio / drawn_ratio;
  }

  draw_matrix_->Reset();
  draw_matrix_->PreTranslate(0.5f, 0.5f);
  if (GetMirror())
    draw_matrix_->PreScale(-1.f, 1.f);
  draw_matrix_->PreScale(scale_x, scale_y);
  draw_matrix_->PreTranslate(-0.5f, -0.5f);

  viewport_width = layout_width_;
  viewport_height = layout_height_;
  viewport_x = (render_width_ - viewport_width)/2;
  viewport_y = (render_height_ - viewport_height)/2;
}

void BaseVideoRender::Release() {
  RTC_LOG(LS_INFO) << TAG << " Release." << GetTid();

  if (frame_drawer_) {
    frame_drawer_.reset();
    frame_drawer_ = nullptr;
  }

  if (egl_render_) {
    egl_render_->Release();
    egl_render_.reset();
    egl_render_ = nullptr;
  }
}

void BaseVideoRender::SetScaleType(webrtc::VideoScaleMode h_match_scale_type,
                                   webrtc::VideoScaleMode h_mismatch_scale_type,
                                   webrtc::VideoScaleMode v_match_scale_type,
                                   webrtc::VideoScaleMode v_mismatch_scale_type) {
  RTC_LOG(LS_INFO) << "SetScaleType. h_match: " << (int) h_match_scale_type << ", h_mismatch: "
                              << (int) h_mismatch_scale_type << ", v_match: " << (int) v_match_scale_type << ", v_mismatch: " << (int) v_mismatch_scale_type
                              << GetTid();
  video_size_measure_->SetScaleType(h_match_scale_type, h_mismatch_scale_type, v_match_scale_type, v_mismatch_scale_type);
  UpdateLayoutAspectRatio();
}

void BaseVideoRender::SetMirror(bool mirror) {
  mirror_ = mirror;
}

bool BaseVideoRender::GetMirror() const {
  return mirror_;
}

std::string BaseVideoRender::GetTid() const{
  return tid_;
}

bool BaseVideoRender::Inited() const {
  return inited_;
}

void BaseVideoRender::UpdateLayoutAspectRatio() {
  if (surface_width_ != 0 && surface_height_ != 0 && frame_width_ != 0 && frame_height_ != 0) {
    int pre_layout_width = layout_width_;
    int pre_layout_height = layout_height_;
    {
      render_width_ = surface_width_;
      render_height_ = surface_height_;
      
      std::pair<int, int> size = video_size_measure_->Measure(
          render_width_, render_height_,
          frame_width_, frame_height_);
      layout_width_ = size.first;
      layout_height_ = size.second;
      layout_aspect_ratio_ = static_cast<float>(layout_width_) / layout_height_;

     RTC_LOG(LS_INFO) << TAG << " UpdateLayoutAspectRatio, layout: " << layout_width_ << "x" << layout_height_
      << ", pre: " << pre_layout_width << "x" << pre_layout_height
      << ", render: " << render_width_ << "x" << render_height_
      << ", surface: " << surface_width_ << "x" << surface_height_
      << ", tid: "
      << GetTid();
    }
  }
}

void BaseVideoRender::UpdateFrameDimensions(int frame_width, int frame_height, int frame_rotation) {
  if (!is_first_frame_rendered_) {
    is_first_frame_rendered_ = true;
    RTC_LOG(LS_INFO) << TAG << " Reporting first rendered frame." << GetTid();
  }
  bool frame_width_change  = this->frame_width_ != frame_width;
  bool frame_height_change = this->frame_height_ != frame_height;
  bool frame_rotation_change = this->frame_rotation_ != frame_rotation;

  if (frame_width_change || frame_height_change || frame_rotation_change) {
    RTC_LOG(LS_INFO) << TAG << " Reporting frame resolution changed to " <<
        frame_width << "x" << frame_height << " with rotation " << frame_rotation
        << ", from " << this->frame_width_ << "x" << this->frame_height_ << " with rotation " << this->frame_rotation_
        << GetTid();

    this->frame_width_ = frame_width;
    this->frame_height_ = frame_height;
    this->frame_rotation_ = frame_rotation;

    if (frame_width_change || frame_height_change) {
      UpdateLayoutAspectRatio();
    }
  }
}

void BaseVideoRender::AddRenderListener(std::shared_ptr<VideoRenderListener> listener) {
  auto it = std::find(render_listeners_.begin(), render_listeners_.end(), listener);
  if (it == render_listeners_.end()) {
    render_listeners_.push_back(listener);
  }
}

void BaseVideoRender::RemoveRenderListener(std::shared_ptr<VideoRenderListener> listener) {
  auto it = std::find(render_listeners_.begin(), render_listeners_.end(), listener);
  if (it != render_listeners_.end()) {
    render_listeners_.erase(it);
  }
}

}